home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / Mail / mimeDecode.php < prev    next >
PHP Script  |  2004-03-24  |  30KB  |  808 lines

  1. <?Php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002  Richard Heyes                                     |
  4. // | All rights reserved.                                                  |
  5. // |                                                                       |
  6. // | Redistribution and use in source and binary forms, with or without    |
  7. // | modification, are permitted provided that the following conditions    |
  8. // | are met:                                                              |
  9. // |                                                                       |
  10. // | o Redistributions of source code must retain the above copyright      |
  11. // |   notice, this list of conditions and the following disclaimer.       |
  12. // | o Redistributions in binary form must reproduce the above copyright   |
  13. // |   notice, this list of conditions and the following disclaimer in the |
  14. // |   documentation and/or other materials provided with the distribution.|
  15. // | o The names of the authors may not be used to endorse or promote      |
  16. // |   products derived from this software without specific prior written  |
  17. // |   permission.                                                         |
  18. // |                                                                       |
  19. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  20. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  21. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  22. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  23. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  24. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  25. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  26. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  27. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  28. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  29. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  30. // |                                                                       |
  31. // +-----------------------------------------------------------------------+
  32. // | Author: Richard Heyes <richard@phpguru.org>                           |
  33. // +-----------------------------------------------------------------------+
  34.  
  35. require_once 'PEAR.php';
  36.  
  37. /**
  38. *  +----------------------------- IMPORTANT ------------------------------+
  39. *  | Usage of this class compared to native php extensions such as        |
  40. *  | mailparse or imap, is slow and may be feature deficient. If available|
  41. *  | you are STRONGLY recommended to use the php extensions.              |
  42. *  +----------------------------------------------------------------------+
  43. *
  44. * Mime Decoding class
  45. *
  46. * This class will parse a raw mime email and return
  47. * the structure. Returned structure is similar to
  48. * that returned by imap_fetchstructure().
  49. *
  50. * USAGE: (assume $input is your raw email)
  51. *
  52. * $decode = new Mail_mimeDecode($input, "\r\n");
  53. * $structure = $decode->decode();
  54. * print_r($structure);
  55. *
  56. * Or statically:
  57. *
  58. * $params['input'] = $input;
  59. * $structure = Mail_mimeDecode::decode($params);
  60. * print_r($structure);
  61. *
  62. * TODO:
  63. *  - Implement further content types, eg. multipart/parallel,
  64. *    perhaps even message/partial.
  65. *
  66. * @author  Richard Heyes <richard@phpguru.org>
  67. * @version $Revision: 1.34 $
  68. * @package Mail
  69. */
  70.  
  71. class Mail_mimeDecode extends PEAR
  72. {
  73.  
  74.     /**
  75.      * The raw email to decode
  76.      * @var    string
  77.      */
  78.     var $_input;
  79.  
  80.     /**
  81.      * The header part of the input
  82.      * @var    string
  83.      */
  84.     var $_header;
  85.  
  86.     /**
  87.      * The body part of the input
  88.      * @var    string
  89.      */
  90.     var $_body;
  91.  
  92.     /**
  93.      * If an error occurs, this is used to store the message
  94.      * @var    string
  95.      */
  96.     var $_error;
  97.  
  98.     /**
  99.      * Flag to determine whether to include bodies in the
  100.      * returned object.
  101.      * @var    boolean
  102.      */
  103.     var $_include_bodies;
  104.  
  105.     /**
  106.      * Flag to determine whether to decode bodies
  107.      * @var    boolean
  108.      */
  109.     var $_decode_bodies;
  110.  
  111.     /**
  112.      * Flag to determine whether to decode headers
  113.      * @var    boolean
  114.      */
  115.     var $_decode_headers;
  116.  
  117.     /**
  118.     * If invoked from a class, $this will be set. This has problematic
  119.     * connotations for calling decode() statically. Hence this variable
  120.     * is used to determine if we are indeed being called statically or
  121.     * via an object.
  122.     */
  123.     var $mailMimeDecode;
  124.  
  125.     /**
  126.      * Constructor.
  127.      *
  128.      * Sets up the object, initialise the variables, and splits and
  129.      * stores the header and body of the input.
  130.      *
  131.      * @param string The input to decode
  132.      * @access public
  133.      */
  134.     function Mail_mimeDecode($input)
  135.     {
  136.         list($header, $body)   = $this->_splitBodyHeader($input);
  137.  
  138.         $this->_input          = $input;
  139.         $this->_header         = $header;
  140.         $this->_body           = $body;
  141.         $this->_decode_bodies  = false;
  142.         $this->_include_bodies = true;
  143.         
  144.         $this->mailMimeDecode  = true;
  145.     }
  146.  
  147.     /**
  148.      * Begins the decoding process. If called statically
  149.      * it will create an object and call the decode() method
  150.      * of it.
  151.      *
  152.      * @param array An array of various parameters that determine
  153.      *              various things:
  154.      *              include_bodies - Whether to include the body in the returned
  155.      *                               object.
  156.      *              decode_bodies  - Whether to decode the bodies
  157.      *                               of the parts. (Transfer encoding)
  158.      *              decode_headers - Whether to decode headers
  159.      *              input          - If called statically, this will be treated
  160.      *                               as the input
  161.      * @return object Decoded results
  162.      * @access public
  163.      */
  164.     function decode($params = null)
  165.     {
  166.  
  167.         // Have we been called statically? If so, create an object and pass details to that.
  168.         if (!isset($this->mailMimeDecode) AND isset($params['input'])) {
  169.  
  170.             $obj = new Mail_mimeDecode($params['input']);
  171.             $structure = $obj->decode($params);
  172.  
  173.         // Called statically but no input
  174.         } elseif (!isset($this->mailMimeDecode)) {
  175.             return PEAR::raiseError('Called statically and no input given');
  176.  
  177.         // Called via an object
  178.         } else {
  179.             $this->_include_bodies = isset($params['include_bodies'])  ? $params['include_bodies']  : false;
  180.             $this->_decode_bodies  = isset($params['decode_bodies'])   ? $params['decode_bodies']   : false;
  181.             $this->_decode_headers = isset($params['decode_headers'])  ? $params['decode_headers']  : false;
  182.  
  183.             $structure = $this->_decode($this->_header, $this->_body);
  184.             if ($structure === false) {
  185.                 $structure = $this->raiseError($this->_error);
  186.             }
  187.         }
  188.  
  189.         return $structure;
  190.     }
  191.  
  192.     /**
  193.      * Performs the decoding. Decodes the body string passed to it
  194.      * If it finds certain content-types it will call itself in a
  195.      * recursive fashion
  196.      *
  197.      * @param string Header section
  198.      * @param string Body section
  199.      * @return object Results of decoding process
  200.      * @access private
  201.      */
  202.     function _decode($headers, $body, $default_ctype = 'text/plain')
  203.     {
  204.         $return = new stdClass;
  205.         $headers = $this->_parseHeaders($headers);
  206.  
  207.         foreach ($headers as $value) {
  208.             if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
  209.                 $return->headers[strtolower($value['name'])]   = array($return->headers[strtolower($value['name'])]);
  210.                 $return->headers[strtolower($value['name'])][] = $value['value'];
  211.  
  212.             } elseif (isset($return->headers[strtolower($value['name'])])) {
  213.                 $return->headers[strtolower($value['name'])][] = $value['value'];
  214.  
  215.             } else {
  216.                 $return->headers[strtolower($value['name'])] = $value['value'];
  217.             }
  218.         }
  219.  
  220.         reset($headers);
  221.         while (list($key, $value) = each($headers)) {
  222.             $headers[$key]['name'] = strtolower($headers[$key]['name']);
  223.             switch ($headers[$key]['name']) {
  224.  
  225.                 case 'content-type':
  226.                     $content_type = $this->_parseHeaderValue($headers[$key]['value']);
  227.  
  228.                     if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
  229.                         $return->ctype_primary   = $regs[1];
  230.                         $return->ctype_secondary = $regs[2];
  231.                     }
  232.  
  233.                     if (isset($content_type['other'])) {
  234.                         while (list($p_name, $p_value) = each($content_type['other'])) {
  235.                             $return->ctype_parameters[$p_name] = $p_value;
  236.                         }
  237.                     }
  238.                     break;
  239.  
  240.                 case 'content-disposition';
  241.                     $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
  242.                     $return->disposition   = $content_disposition['value'];
  243.                     if (isset($content_disposition['other'])) {
  244.                         while (list($p_name, $p_value) = each($content_disposition['other'])) {
  245.                             $return->d_parameters[$p_name] = $p_value;
  246.                         }
  247.                     }
  248.                     break;
  249.  
  250.                 case 'content-transfer-encoding':
  251.                     $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
  252.                     break;
  253.             }
  254.         }
  255.  
  256.         if (isset($content_type)) {
  257.             switch (strtolower($content_type['value'])) {
  258.                 case 'text/plain':
  259.                     $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  260.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  261.                     break;
  262.  
  263.                 case 'text/html':
  264.                     $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  265.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
  266.                     break;
  267.  
  268.                 case 'multipart/parallel':
  269.                 case 'multipart/report': // RFC1892
  270.                 case 'multipart/signed': // PGP
  271.                 case 'multipart/digest':
  272.                 case 'multipart/alternative':
  273.                 case 'multipart/related':
  274.                 case 'multipart/mixed':
  275.                     if(!isset($content_type['other']['boundary'])){
  276.                         $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
  277.                         return false;
  278.                     }
  279.  
  280.                     $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
  281.  
  282.                     $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
  283.                     for ($i = 0; $i < count($parts); $i++) {
  284.                         list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
  285.                         $part = $this->_decode($part_header, $part_body, $default_ctype);
  286.                         if($part === false)
  287.                             $part = $this->raiseError($this->_error);
  288.                         $return->parts[] = $part;
  289.                     }
  290.                     break;
  291.  
  292.                 case 'message/rfc822':
  293.                     $obj = &new Mail_mimeDecode($body);
  294.                     $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies));
  295.                     unset($obj);
  296.                     break;
  297.  
  298.                 default:
  299.                     if(!isset($content_transfer_encoding['value']))
  300.                         $content_transfer_encoding['value'] = '7bit';
  301.                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
  302.                     break;
  303.             }
  304.  
  305.         } else {
  306.             $ctype = explode('/', $default_ctype);
  307.             $return->ctype_primary   = $ctype[0];
  308.             $return->ctype_secondary = $ctype[1];
  309.             $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
  310.         }
  311.  
  312.         return $return;
  313.     }
  314.  
  315.     /**
  316.      * Given the output of the above function, this will return an
  317.      * array of references to the parts, indexed by mime number.
  318.      *
  319.      * @param  object $structure   The structure to go through
  320.      * @param  string $mime_number Internal use only.
  321.      * @return array               Mime numbers
  322.      */
  323.     function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
  324.     {
  325.         $return = array();
  326.         if (!empty($structure->parts)) {
  327.             if ($mime_number != '') {
  328.                 $structure->mime_id = $prepend . $mime_number;
  329.                 $return[$prepend . $mime_number] = &$structure;
  330.             }
  331.             for ($i = 0; $i < count($structure->parts); $i++) {
  332.  
  333.             
  334.                 if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
  335.                     $prepend      = $prepend . $mime_number . '.';
  336.                     $_mime_number = '';
  337.                 } else {
  338.                     $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
  339.                 }
  340.  
  341.                 $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
  342.                 foreach ($arr as $key => $val) {
  343.                     $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
  344.                 }
  345.             }
  346.         } else {
  347.             if ($mime_number == '') {
  348.                 $mime_number = '1';
  349.             }
  350.             $structure->mime_id = $prepend . $mime_number;
  351.             $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
  352.         }
  353.         
  354.         return $return;
  355.     }
  356.  
  357.     /**
  358.      * Given a string containing a header and body
  359.      * section, this function will split them (at the first
  360.      * blank line) and return them.
  361.      *
  362.      * @param string Input to split apart
  363.      * @return array Contains header and body section
  364.      * @access private
  365.      */
  366.     function _splitBodyHeader($input)
  367.     {
  368.         if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
  369.             return array($match[1], $match[2]);
  370.         }
  371.         $this->_error = 'Could not split header and body';
  372.         return false;
  373.     }
  374.  
  375.     /**
  376.      * Parse headers given in $input and return
  377.      * as assoc array.
  378.      *
  379.      * @param string Headers to parse
  380.      * @return array Contains parsed headers
  381.      * @access private
  382.      */
  383.     function _parseHeaders($input)
  384.     {
  385.  
  386.         if ($input !== '') {
  387.             // Unfold the input
  388.             $input   = preg_replace("/\r?\n/", "\r\n", $input);
  389.             $input   = preg_replace("/\r\n(\t| )+/", ' ', $input);
  390.             $headers = explode("\r\n", trim($input));
  391.  
  392.             foreach ($headers as $value) {
  393.                 $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
  394.                 $hdr_value = substr($value, $pos+1);
  395.                 if($hdr_value[0] == ' ')
  396.                     $hdr_value = substr($hdr_value, 1);
  397.  
  398.                 $return[] = array(
  399.                                   'name'  => $hdr_name,
  400.                                   'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
  401.                                  );
  402.             }
  403.         } else {
  404.             $return = array();
  405.         }
  406.  
  407.         return $return;
  408.     }
  409.  
  410.     /**
  411.      * Function to parse a header value,
  412.      * extract first part, and any secondary
  413.      * parts (after ;) This function is not as
  414.      * robust as it could be. Eg. header comments
  415.      * in the wrong place will probably break it.
  416.      *
  417.      * @param string Header value to parse
  418.      * @return array Contains parsed result
  419.      * @access private
  420.      */
  421.     function _parseHeaderValue($input)
  422.     {
  423.  
  424.         if (($pos = strpos($input, ';')) !== false) {
  425.  
  426.             $return['value'] = trim(substr($input, 0, $pos));
  427.             $input = trim(substr($input, $pos+1));
  428.  
  429.             if (strlen($input) > 0) {
  430.  
  431.                 // This splits on a semi-colon, if there's no preceeding backslash
  432.                 // Can't handle if it's in double quotes however. (Of course anyone
  433.                 // sending that needs a good slap).
  434.                 $parameters = preg_split('/\s*(?<!\\\\);\s*/i', $input);
  435.  
  436.                 for ($i = 0; $i < count($parameters); $i++) {
  437.                     $param_name  = substr($parameters[$i], 0, $pos = strpos($parameters[$i], '='));
  438.                     $param_value = substr($parameters[$i], $pos + 1);
  439.                     if ($param_value[0] == '"') {
  440.                         $param_value = substr($param_value, 1, -1);
  441.                     }
  442.                     $return['other'][$param_name] = $param_value;
  443.                     $return['other'][strtolower($param_name)] = $param_value;
  444.                 }
  445.             }
  446.         } else {
  447.             $return['value'] = trim($input);
  448.         }
  449.  
  450.         return $return;
  451.     }
  452.  
  453.     /**
  454.      * This function splits the input based
  455.      * on the given boundary
  456.      *
  457.      * @param string Input to parse
  458.      * @return array Contains array of resulting mime parts
  459.      * @access private
  460.      */
  461.     function _boundarySplit($input, $boundary)
  462.     {
  463.         $tmp = explode('--'.$boundary, $input);
  464.  
  465.         for ($i=1; $i<count($tmp)-1; $i++) {
  466.             $parts[] = $tmp[$i];
  467.         }
  468.  
  469.         return $parts;
  470.     }
  471.  
  472.     /**
  473.      * Given a header, this function will decode it
  474.      * according to RFC2047. Probably not *exactly*
  475.      * conformant, but it does pass all the given
  476.      * examples (in RFC2047).
  477.      *
  478.      * @param string Input header value to decode
  479.      * @return string Decoded header value
  480.      * @access private
  481.      */
  482.     function _decodeHeader($input)
  483.     {
  484.         // Remove white space between encoded-words
  485.         $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
  486.  
  487.         // For each encoded-word...
  488.         while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
  489.  
  490.             $encoded  = $matches[1];
  491.             $charset  = $matches[2];
  492.             $encoding = $matches[3];
  493.             $text     = $matches[4];
  494.  
  495.             switch (strtolower($encoding)) {
  496.                 case 'b':
  497.                     $text = base64_decode($text);
  498.                     break;
  499.  
  500.                 case 'q':
  501.                     $text = str_replace('_', ' ', $text);
  502.                     preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
  503.                     foreach($matches[1] as $value)
  504.                         $text = str_replace('='.$value, chr(hexdec($value)), $text);
  505.                     break;
  506.             }
  507.  
  508.             $input = str_replace($encoded, $text, $input);
  509.         }
  510.  
  511.         return $input;
  512.     }
  513.  
  514.     /**
  515.      * Given a body string and an encoding type,
  516.      * this function will decode and return it.
  517.      *
  518.      * @param  string Input body to decode
  519.      * @param  string Encoding type to use.
  520.      * @return string Decoded body
  521.      * @access private
  522.      */
  523.     function _decodeBody($input, $encoding = '7bit')
  524.     {
  525.         switch ($encoding) {
  526.             case '7bit':
  527.                 return $input;
  528.                 break;
  529.  
  530.             case 'quoted-printable':
  531.                 return $this->_quotedPrintableDecode($input);
  532.                 break;
  533.  
  534.             case 'base64':
  535.                 return base64_decode($input);
  536.                 break;
  537.  
  538.             default:
  539.                 return $input;
  540.         }
  541.     }
  542.  
  543.     /**
  544.      * Given a quoted-printable string, this
  545.      * function will decode and return it.
  546.      *
  547.      * @param  string Input body to decode
  548.      * @return string Decoded body
  549.      * @access private
  550.      */
  551.     function _quotedPrintableDecode($input)
  552.     {
  553.         // Remove soft line breaks
  554.         $input = preg_replace("/=\r?\n/", '', $input);
  555.  
  556.         // Replace encoded characters
  557.         $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
  558.  
  559.         return $input;
  560.     }
  561.  
  562.     /**
  563.      * Checks the input for uuencoded files and returns
  564.      * an array of them. Can be called statically, eg:
  565.      *
  566.      * $files =& Mail_mimeDecode::uudecode($some_text);
  567.      *
  568.      * It will check for the begin 666 ... end syntax
  569.      * however and won't just blindly decode whatever you
  570.      * pass it.
  571.      *
  572.      * @param  string Input body to look for attahcments in
  573.      * @return array  Decoded bodies, filenames and permissions
  574.      * @access public
  575.      * @author Unknown
  576.      */
  577.     function &uudecode($input)
  578.     {
  579.         // Find all uuencoded sections
  580.         preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
  581.  
  582.         for ($j = 0; $j < count($matches[3]); $j++) {
  583.  
  584.             $str      = $matches[3][$j];
  585.             $filename = $matches[2][$j];
  586.             $fileperm = $matches[1][$j];
  587.  
  588.             $file = '';
  589.             $str = preg_split("/\r?\n/", trim($str));
  590.             $strlen = count($str);
  591.  
  592.             for ($i = 0; $i < $strlen; $i++) {
  593.                 $pos = 1;
  594.                 $d = 0;
  595.                 $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
  596.  
  597.                 while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
  598.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  599.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  600.                     $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  601.                     $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
  602.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  603.  
  604.                     $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  605.  
  606.                     $file .= chr(((($c2 - ' ') & 077) << 6) |  (($c3 - ' ') & 077));
  607.  
  608.                     $pos += 4;
  609.                     $d += 3;
  610.                 }
  611.  
  612.                 if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
  613.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  614.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  615.                     $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
  616.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  617.  
  618.                     $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
  619.  
  620.                     $pos += 3;
  621.                     $d += 2;
  622.                 }
  623.  
  624.                 if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
  625.                     $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
  626.                     $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
  627.                     $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
  628.  
  629.                 }
  630.             }
  631.             $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
  632.         }
  633.  
  634.         return $files;
  635.     }
  636.  
  637.     /**
  638.      * getSendArray() returns the arguments required for Mail::send()
  639.      * used to build the arguments for a mail::send() call 
  640.      *
  641.      * Usage:
  642.      * $mailtext = Full email (for example generated by a template)
  643.      * $decoder = new Mail_mimeDecode($mailtext);
  644.      * $parts =  $decoder->getSendArray();
  645.      * if (!PEAR::isError($parts) {
  646.      *     list($recipents,$headers,$body) = $parts;
  647.      *     $mail = Mail::factory('smtp');
  648.      *     $mail->send($recipents,$headers,$body);
  649.      * } else {
  650.      *     echo $parts->message;
  651.      * }
  652.      * @return mixed   array of recipeint, headers,body or Pear_Error
  653.      * @access public
  654.      * @author Alan Knowles <alan@akbkhome.com>
  655.      */
  656.     function getSendArray()
  657.     {
  658.         // prevent warning if this is not set
  659.         $this->_decode_headers = FALSE;
  660.         $headerlist =$this->_parseHeaders($this->_header);
  661.         $to = "";
  662.         if (!$headerlist) {
  663.             return $this->raiseError("Message did not contain headers");
  664.         }
  665.         foreach($headerlist as $item) {
  666.             $header[$item['name']] = $item['value'];
  667.             switch (strtolower($item['name'])) {
  668.                 case "to":
  669.                 case "cc":
  670.                 case "bcc":
  671.                     $to = ",".$item['value'];
  672.                 default:
  673.                    break;
  674.             }
  675.         }
  676.         if ($to == "") {
  677.             return $this->raiseError("Message did not contain any recipents");
  678.         }
  679.         $to = substr($to,1);
  680.         return array($to,$header,$this->_body);
  681.     } 
  682.  
  683.  
  684.  
  685.  
  686.  
  687.  
  688.  
  689.  
  690.  
  691.     /**
  692.      * Returns a xml copy of the output of
  693.      * Mail_mimeDecode::decode. Pass the output in as the
  694.      * argument. This function can be called statically. Eg:
  695.      *
  696.      * $output = $obj->decode();
  697.      * $xml    = Mail_mimeDecode::getXML($output);
  698.      *
  699.      * The DTD used for this should have been in the package. Or
  700.      * alternatively you can get it from cvs, or here:
  701.      * http://www.phpguru.org/xmail/xmail.dtd.
  702.      *
  703.      * @param  object Input to convert to xml. This should be the
  704.      *                output of the Mail_mimeDecode::decode function
  705.      * @return string XML version of input
  706.      * @access public
  707.      */
  708.     function getXML($input)
  709.     {
  710.         $crlf    =  "\r\n";
  711.         $output  = '<?xml version=\'1.0\'?>' . $crlf .
  712.                    '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
  713.                    '<email>' . $crlf .
  714.                    Mail_mimeDecode::_getXML($input) .
  715.                    '</email>';
  716.  
  717.         return $output;
  718.     }
  719.  
  720.     /**
  721.      * Function that does the actual conversion to xml. Does a single
  722.      * mimepart at a time.
  723.      *
  724.      * @param  object  Input to convert to xml. This is a mimepart object.
  725.      *                 It may or may not contain subparts.
  726.      * @param  integer Number of tabs to indent
  727.      * @return string  XML version of input
  728.      * @access private
  729.      */
  730.     function _getXML($input, $indent = 1)
  731.     {
  732.         $htab    =  "\t";
  733.         $crlf    =  "\r\n";
  734.         $output  =  '';
  735.         $headers = @(array)$input->headers;
  736.  
  737.         foreach ($headers as $hdr_name => $hdr_value) {
  738.  
  739.             // Multiple headers with this name
  740.             if (is_array($headers[$hdr_name])) {
  741.                 for ($i = 0; $i < count($hdr_value); $i++) {
  742.                     $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
  743.                 }
  744.  
  745.             // Only one header of this sort
  746.             } else {
  747.                 $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
  748.             }
  749.         }
  750.  
  751.         if (!empty($input->parts)) {
  752.             for ($i = 0; $i < count($input->parts); $i++) {
  753.                 $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
  754.                            Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
  755.                            str_repeat($htab, $indent) . '</mimepart>' . $crlf;
  756.             }
  757.         } elseif (isset($input->body)) {
  758.             $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
  759.                        $input->body . ']]></body>' . $crlf;
  760.         }
  761.  
  762.         return $output;
  763.     }
  764.  
  765.     /**
  766.      * Helper function to _getXML(). Returns xml of a header.
  767.      *
  768.      * @param  string  Name of header
  769.      * @param  string  Value of header
  770.      * @param  integer Number of tabs to indent
  771.      * @return string  XML version of input
  772.      * @access private
  773.      */
  774.     function _getXML_helper($hdr_name, $hdr_value, $indent)
  775.     {
  776.         $htab   = "\t";
  777.         $crlf   = "\r\n";
  778.         $return = '';
  779.  
  780.         $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
  781.         $new_hdr_name  = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
  782.  
  783.         // Sort out any parameters
  784.         if (!empty($new_hdr_value['other'])) {
  785.             foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
  786.                 $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
  787.                             str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
  788.                             str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
  789.                             str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
  790.             }
  791.  
  792.             $params = implode('', $params);
  793.         } else {
  794.             $params = '';
  795.         }
  796.  
  797.         $return = str_repeat($htab, $indent) . '<header>' . $crlf .
  798.                   str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
  799.                   str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
  800.                   $params .
  801.                   str_repeat($htab, $indent) . '</header>' . $crlf;
  802.  
  803.         return $return;
  804.     }
  805.  
  806. } // End of class
  807. ?>
  808.